<!DOCTYPE html>
<html>

<head>
  <title>File-Server.local</title>
  <link rel="modulepreload" href="/_lib_/www/request.js">
  <link rel="stylesheet" href="/_lib_/www/style/common.css">
  <link rel="stylesheet" href="/_lib_/www/style/dark.css" media="(prefers-color-scheme: dark)">
  <script src="/_lib_/www/custom-elements.js" type="module"></script>
</head>
<body>
  <main style="margin: 10% auto;">
    <section>
      <div>
        <label for="get-file-input">What do you want?</label>
        <input type="search" id="get-file-input" name="q" pattern="[^|>&*']*">
        <button id="download-button">download</button>
        <dirent-list id="get-file-input-datalist" for="get-file-input"></dirent-list>
        <div>
          <input type="checkbox" id="is-range-request" name="r">
          <label for="is-range-request">Use multithreaded download</label>

          <span id="threads-count-wrapper" style="visibility: visible;">
            <input type="range" id="threads-count" name="t" min="4" max="32" value="12" step="2"
              style="visibility: inherit;">
            <label for="threads-count" style="visibility: inherit; white-space: pre-wrap;">12 threads</label>
            <small id="multithreads-attention" style="visibility: inherit;">
              Attention: multithreaded download will consume up to 384 MiB RAM
            </small>
          </span>
        </div>
      </div>
      <div>
        <div style="display: flex; justify-content: space-between;">
          <label for="log">Output log:</label>
          <input type="reset" id="clear-log" value="Clear" style="font-size: 0.6rem;">
        </div>
        <textarea id="log" name="log" rows="5" cols="33" placeholder="It was a dark and stormy night..."></textarea>
      </div>
    </section>
    <section style="margin-top: 7rem;">
      <div>
        <label for="file-uploader">...Or Upload file</label>
        <input id="file-uploader" type="file"/>
        <span>to</span>
        <input id="upload-target" type="text" placeholder="optional" maxlength="80" size="25" />
        <dirent-list id="upload-destination-datalist" for="upload-target" folder-only reverse></dirent-list>
        <section style="margin-top: 1rem">
          <div>
            <label for="username" style="display: inline-block; width: 5rem;">Username:</label>
            <input type="text" autocomplete="username" id="username" name="username">
  
            <div>
              <label for="pass" style="display: inline-block; width: 5rem;">Password:</label>
              <input type="password" autocomplete="current-password" id="password" name="password" minlength="8" required>
            </div>
          </div>

          <button type="submit" id="upload-button">upload</button>
        </section>
      </div>
    </section>
  </main>
</body>
</html>
<script type="module">
  /**
   * isRangeRequest style
   */
  const map = { "true": "visible", "false": "hidden" };
  const isRangeRequest = document.getElementById("is-range-request");
  const threadsCountWrapper = document.getElementById("threads-count-wrapper");

  isRangeRequest.onchange = () => {
    threadsCountWrapper.style.visibility = map[
      String(Boolean(isRangeRequest.checked))
    ];
    localStorage["isRangeRequest.checked"] = String(Boolean(isRangeRequest.checked));
  };

  isRangeRequest.checked = localStorage["isRangeRequest.checked"] !== "false";
  isRangeRequest.onchange();

  /**
   * threadsSelector style
   */
  const threadsSelector = document.getElementById("threads-count");
  const threadsSelectorLabel = threadsSelector.parentNode.querySelector('label[for="threads-count"]');
  const multithreadsAttention = threadsSelector.parentNode.querySelector('#multithreads-attention');
  threadsSelector.onchange = () => {
    const v = threadsSelector.value.length === 1 ? "  ".concat(threadsSelector.value) : threadsSelector.value;
    threadsSelectorLabel.textContent = `${v} threads`;
    multithreadsAttention.innerText =
      multithreadsAttention.innerText.replace(/\d+(?=\sMiB\sRAM$)/i, Number(threadsSelector.value) * 32)
      ;
    localStorage["threadsSelector.value"] = threadsSelector.value;
  };
  threadsSelector.value = Number(localStorage["threadsSelector.value"]);
  threadsSelector.onchange();

  /**
   * log style
   */
  const log = document.getElementById("log");

  log.value = "$: ";
  document.getElementById("clear-log").addEventListener("click", () => log.value = "$:", { passive: true });

  function logAppend(...argv) {
    log.value += argv.join(" ").concat("\n");
  }

  /**
   * upload style
   */
  const username = document.getElementById("username");
  const password = document.getElementById("password");

  username.addEventListener("keyup", event => {
    if (event.key === "ArrowDown" || event.key === "Enter") {
      password.focus();
    }
  });

  password.addEventListener("keyup", event => {
    if (event.key === "ArrowUp") {
      username.focus();
    }
    if (event.key === "Enter") {
      uploadButton.click();
    }
  });

  if (localStorage["username"])
    username.value = localStorage["username"];

  /**
   * networking
   */
  import {
    download,
    upload,
    DirentListFetcher,
    ProgressLog
  } from "/_lib_/www/request.js";

  const beforeUnloadHandler = event => {
    event.preventDefault();
    return event.returnValue = "Are you sure you want to exit?";
  };

  function couplingDownload() {
    if (!pathInput.value)
      return logAppend("input required.");

    const pathname = pathInput.value
      .replace(/[\\\/]+/, "/")
      .replace(/^([^\/])/, "/$1")
      ;

    if (!ProgressLog.instance) new ProgressLog(log);

    window.addEventListener("beforeunload", beforeUnloadHandler);

    return (
      download(pathname, isRangeRequest.checked, threadsSelector.value)
        .catch(err => {
          console.error(err);
          setTimeout(() => logAppend(err.message), 20);
        })
        .finally(
          () => window.removeEventListener("beforeunload", beforeUnloadHandler)
        )
    );
  }

  const fetcher = new DirentListFetcher();

  let unauthorized = false, authCache;
  async function couplingSetList(path, id, auth, force) {
    if (authCache && !auth) {
      auth = authCache;
    }

    if (unauthorized && !auth)
      return;

    const result = fetcher.updateDirentList(path, document.getElementById(id));

    switch (result) {
      case "ok":
        if(!force)
          return ;
        /* fall through */
      case "empty": return (
        fetcher.fetch(path, auth)
          .then(list => {
            if(auth)
              authCache = auth;

            return fetcher.updateDirentList(path, document.getElementById(id));
          })
          .catch(err => {
            if(err.message.startsWith("401"))
              unauthorized = true;
            else throw err;
          })
      );
      default:
        return ;
    }
  }

  function makeInputAdjustable (input) {
    const initialWidth = input.offsetWidth * 1.5;
    const initialStyle = input.style.width;
    const fontSize = parseFloat(getComputedStyle(input).fontSize);
    const padding = 4;

    input.addEventListener("input", e => {
      const length = input.value.length + padding;
      if(length * fontSize > initialWidth) {
        input.style.width = String(length).concat("ch");
      } else {
        input.style.width = initialStyle;
      }
    });
  }

  /**
   * download
   */
  const pathInput = document.getElementById("get-file-input");
  const downloadButton = document.getElementById("download-button");

  downloadButton.addEventListener("click", couplingDownload, { passive: true });
  pathInput.addEventListener("keyup", e => e.key === "Enter" && couplingDownload());

  pathInput.focus();
  makeInputAdjustable(pathInput);

  const direntListId = "get-file-input-datalist";

  couplingSetList("/", direntListId);
  pathInput.addEventListener(
    "input", () => couplingSetList(pathInput.value, direntListId)
  );
  /**
   * upload
   */
  const fileSelect = document.getElementById("file-uploader");
  const uploadDestination = document.getElementById("upload-target");
  const uploadButton = document.getElementById("upload-button");

  uploadButton.addEventListener("click", () => {
    if (!fileSelect.files.length)
      return logAppend("input required");

    localStorage["username"] = username.value;

    const file = fileSelect.files[0];
    const destination = uploadDestination.value || file.name;
    const auth = `Basic ${btoa(`${username.value}:${password.value}`)}`;

    return (
      upload(file, destination, auth)
        .then(async res => logAppend(
          `${res.status} ${await res.text() || res.statusText}`
        ))
        .then(() => couplingSetList(
          "/",
          "get-file-input-datalist",
          auth,
          true
        ))
        .catch(err => logAppend(
          `Upload '${destination}' errored: ${err.message}`
        ))
    );
  });

  makeInputAdjustable(uploadDestination);
  uploadDestination.addEventListener("keyup", e => e.key === "Enter" && uploadButton.click());
  uploadDestination.addEventListener(
    "input", () => couplingSetList(uploadDestination.value, "upload-destination-datalist")
  );
  couplingSetList("/", "upload-destination-datalist")
</script>